package jwebtester.dal.provider.impl;

import infrastructure.Scopes;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import jwebtester.business.services.MapperService;
import jwebtester.dal.entities.CompetencyEntity;
import jwebtester.dal.provider.CompetenciesDataProvider;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.JDBCException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import utils.CollectionUtils;
import domain.entities.Competency;

@Repository
@Scope(value = Scopes.Request, proxyMode = ScopedProxyMode.INTERFACES)
public class CompetenciesDataProviderImpl implements CompetenciesDataProvider {

	private static final Logger LOG = LoggerFactory.getLogger(CompetenciesDataProviderImpl.class);

	@Autowired
	private transient SessionFactory sessionFactory;

	@Autowired
	private transient MapperService mapperService;

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public List<Competency> getRootCompetencies(final int organizationID) {
		final Session session = sessionFactory.getCurrentSession();

		try {
			final List<Competency> result = new ArrayList<Competency>();

			final Query query = session
					.createQuery("select c from CompetencyEntity c where c.organizationEntity.id = :orgID "
							+ " and c.competencyEntity.id is null");
			query.setInteger("orgID", organizationID);

			final List<CompetencyEntity> dataEntities = CollectionUtils.castList(query.list());
			if (dataEntities != null) {
				for (CompetencyEntity dataEntity : dataEntities) {
					final Competency group = (Competency) mapperService.mapTo(dataEntity,
							Competency.class);
					result.add(group);
				}
			}

			return result;
		} catch (RuntimeException ex) {
			LOG.error(String.format("Error loading root competencies. OrganizationID = %s",
					organizationID), ex);
			throw ex;
		}
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public List<Competency> getCompetencies(int organizationID, int parentID) {
		Session session = sessionFactory.getCurrentSession();

		try {
			final List<Competency> result = new ArrayList<Competency>();

			final Query query = session
					.createQuery("select c from CompetencyEntity c where c.organizationEntity.id = :orgID "
							+ " and c.competencyEntity.id = :parentID");
			query.setInteger("orgID", organizationID);
			query.setInteger("parentID", parentID);

			List<CompetencyEntity> dataEntities = CollectionUtils.castList(query.list());
			if (dataEntities != null) {
				for (CompetencyEntity dataEntity : dataEntities) {
					Competency group = (Competency) mapperService.mapTo(dataEntity,
							Competency.class);
					result.add(group);
				}
			}

			return result;
		} catch (RuntimeException ex) {
			LOG.error(String.format(
					"Error loading root competencies. OrganizationID = %s, ParentID = %s",
					organizationID, parentID), ex);
			throw ex;
		}
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public Competency getHierarchy(int organizationID) {
		final Session session = sessionFactory.getCurrentSession();

		try {
			Query query = session
					.createQuery("from CompetencyEntity c where c.organizationEntity.id = :orgID");
			query.setInteger("orgID", organizationID);

			List<CompetencyEntity> dataEntities = CollectionUtils.castList(query.list());
			Map<Integer, CompetencyEntity> cache = new HashMap<Integer, CompetencyEntity>();

			for (CompetencyEntity dataEntity : dataEntities) {
				cache.put(dataEntity.getId(), dataEntity);
			}

			// build tree
			Competency root = new Competency(Competency.ROOT_ID, StringUtils.EMPTY);
			List<Competency> childs = getRootCompetencies(dataEntities);
			root.setChilds(childs);

			Stack<Competency> stack = CollectionUtils.asStack(childs);
			while (stack.size() > 0) {
				Competency top = stack.pop();

				List<Competency> subChilds = getByParentID(top.getId(), cache);
				top.setChilds(subChilds);
				for (Competency c : subChilds) {
					stack.push(c);
				}
			}

			return root;
		} catch (RuntimeException ex) {
			LOG.error(String.format("Error loading competencies hierarchy. OrganizationID = %s",
					organizationID), ex);
			throw ex;
		}
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public Competency getCompetency(final int competencyID) {
		final Session session = sessionFactory.getCurrentSession();

		try {
			CompetencyEntity dataEntity = (CompetencyEntity) session.get(CompetencyEntity.class,
					competencyID);
			Competency businessEntity = (Competency) mapperService.mapTo(dataEntity,
					Competency.class);
			return businessEntity;
		} catch (RuntimeException ex) {
			LOG.error(String.format("Error loading competency id=%s", competencyID), ex);
		}
		
		return null;
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public Competency getCompetencyByName(int organizationID, String name) {
		Session session = sessionFactory.getCurrentSession();

		try {
			Query query = session
					.createQuery("from CompetencyEntity c where c.organizationEntity.id = :orgID "
							+ "and c.name = :competencyName");
			query.setInteger("orgID", organizationID);
			query.setString("competencyName", name);

			CompetencyEntity dataEntity = (CompetencyEntity) query.uniqueResult();
			Competency businessEntity = (Competency) mapperService.mapTo(dataEntity,
					Competency.class);
			return businessEntity;
		} catch (RuntimeException ex) {
			LOG.error(String.format("Error loading competency organizationID=%s, name=%s",
					organizationID, name), ex);
			throw ex;
		}
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public List<Competency> getCompetencies(int organizationID, String namePattern) {
		Session session = sessionFactory.getCurrentSession();

		try {
			final List<Competency> result = new ArrayList<Competency>();

			Query query = session
					.createQuery("from CompetencyEntity c where c.organizationEntity.id = :orgID "
							+ "and c.name like :namePattern");
			query.setInteger("orgID", organizationID);
			query.setString("namePattern", '%' + namePattern + '%');
			query.setMaxResults(15);

			List<CompetencyEntity> dataEntities = CollectionUtils.castList(query.list());
			for (CompetencyEntity dataEntity : dataEntities) {
				Competency c = (Competency) mapperService.mapTo(dataEntity, Competency.class);
				result.add(c);
			}

			return result;
		} catch (RuntimeException ex) {
			LOG.error(String.format("Error finding competencies with name like [%s]", namePattern),
					ex);
			throw ex;
		}
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public int createCompetency(Competency competency) throws JDBCException {
		Session session = sessionFactory.getCurrentSession();

		try {
			CompetencyEntity dataEntity = (CompetencyEntity) mapperService.mapTo(competency,
					CompetencyEntity.class);
			session.save(dataEntity);

			return dataEntity.getId();
		} catch (JDBCException ex) {
			LOG.error(String.format("Error saving new competency %s", competency), ex);
			throw ex;
		}
	}

	@Override
	@Transactional
	public void updateCompetency(Competency competency) throws JDBCException {
		Session session = sessionFactory.getCurrentSession();

		try {
			CompetencyEntity dataEntity = (CompetencyEntity) mapperService.mapTo(competency,
					CompetencyEntity.class);
			session.update(dataEntity);
		} catch (JDBCException ex) {
			LOG.error(String.format("Error updating existing competency %s", competency), ex);
			throw ex;
		}
	}

	@Override
	@Transactional
	public void deleteCompetency(int competencyID) throws JDBCException {
		Session session = sessionFactory.getCurrentSession();

		try {
			CompetencyEntity dataEntity = (CompetencyEntity) session.get(CompetencyEntity.class,
					competencyID);
			if (dataEntity != null) {
				session.delete(dataEntity);
			}
		} catch (JDBCException ex) {
			LOG.error(String.format("Error deleting competency. ID=%s", competencyID), ex);
			throw ex;
		}
	}

	private List<Competency> getRootCompetencies(List<CompetencyEntity> all) {
		List<Competency> result = new ArrayList<Competency>();

		for (CompetencyEntity dataEntity : all) {
			if (dataEntity.getCompetencyEntity() == null) {
				Competency competency = (Competency) mapperService.mapTo(dataEntity,
						Competency.class);
				result.add(competency);
			}
		}

		return result;
	}

	private List<Competency> getByParentID(int parentID, Map<Integer, CompetencyEntity> cache) {
		List<Competency> result = new ArrayList<Competency>();

		Collection<CompetencyEntity> values = cache.values();
		for (CompetencyEntity dataEntity : values) {
			CompetencyEntity parent = dataEntity.getCompetencyEntity();
			if (parent != null && parent.getId() == parentID) {
				Competency competency = (Competency) mapperService.mapTo(dataEntity,
						Competency.class);
				result.add(competency);
			}
		}

		return result;
	}

}
